package net.sf.fmj.ejmf.toolkit.gui.controls;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventListener;

import javax.media.Controller;
import javax.media.GainChangeEvent;
import javax.media.GainChangeListener;
import javax.media.GainControl;
import javax.swing.AbstractButton;
import javax.swing.SwingUtilities;

/**
 * Gain Control for Standard Control Panel. Provides two Components, one each
 * for increasing and decreasing gain.
 * 
 * @see net.sf.fmj.ejmf.toolkit.gui.controls.AbstractGainControl
 */

public class StandardGainControl extends AbstractGainControl implements
		GainChangeListener {

	/**
	 * Create a StandardGainControl and associate it with a Controller.
	 * 
	 * @param controller
	 *            A Controller with which control is associated.
	 */
	public StandardGainControl(Skin skin, Controller controller) {
		super(skin, controller);
	}

	/** Create a StandardGainControl */
	public StandardGainControl(Skin skin) {
		super(skin);
	}

	/**
	 * This method is called when <code>setController</code> is called on an
	 * AbstractListenerControl.
	 * 
	 * @param newController
	 *            A Controller with which this control is associated.
	 */
	protected void setControllerHook(Controller newController) {
		super.setControllerHook(newController);

		GainControl gc;
		if (isOperational()) {
			gc = getGainControl();
			setState(gc.getLevel());
			gc.addGainChangeListener(this);
		}
	}

	/**
	 * Create Control Component for Gain Control.
	 * 
	 * @return component which acts as gain control.
	 */
	@Override
	protected Component createControlComponent(Skin skin) {
		return new StandardGainButtonPanel(skin);
	}

	/**
	 * Over-rides addControlListener from AbstractActionListener because this
	 * Component is a JPanel and buttons need to be extracted from it. Same
	 * listener is posted to both buttons.
	 * 
	 * @param listener
	 *            Listener that implements semantics for gain control.
	 */
	protected void addControlListener(EventListener listener) {
		if (listener instanceof ActionListener) {
			AbstractButton ab;
			AbstractGainButtonPanel p = (AbstractGainButtonPanel) getControlComponent();

			ab = p.getGainIncreaseButton();
			ab.addActionListener((ActionListener) listener);
			ab = p.getGainDecreaseButton();
			ab.addActionListener((ActionListener) listener);
		} else {
			throw new IllegalArgumentException("ActionListener required");
		}
	}

	/**
	 * Remove listener from both gain increase button and gain decrease button.
	 * 
	 * @param listener
	 *            Listener is removed from listener list associated with this
	 *            control.
	 */
	protected void removeControlListener(EventListener listener) {
		if (listener instanceof ActionListener) {
			AbstractButton ab;
			StandardGainButtonPanel p = (StandardGainButtonPanel) getControlComponent();

			ab = p.getGainIncreaseButton();
			ab.removeActionListener((ActionListener) listener);
			ab = p.getGainDecreaseButton();
			ab.removeActionListener((ActionListener) listener);
		} else {
			throw new IllegalArgumentException("ActionListener required");
		}
	}

	/**
	 * Create and return the default listener for gain control. Default listener
	 * increases gain level by 0.1 for each click of up button and decreases
	 * gain level by 0.1 for each click of down button.
	 * 
	 * @return Default listener for gain control.
	 */
	protected EventListener createControlListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				AbstractGainButtonPanel gbp = (AbstractGainButtonPanel) getControlComponent();
				GainControl gc = getGainControl();
				float level = gc.getLevel();

				if (e.getSource() == gbp.getGainIncreaseButton()) {
					level += 0.1f;
					gc.setLevel(level > 1.0f ? 1.0f : level);
				} else if (e.getSource() == gbp.getGainDecreaseButton()) {
					level -= 0.1f;
					gc.setLevel(level < 0.0f ? 0.0f : level);
				}
			}
		};
	}

	/**
	 * Set enable state of gain buttons based on gain level. If level has
	 * reached maximum, disable increase button. If level has reached minimum,
	 * enable descrease button.
	 * 
	 * @param e
	 *            An GainChangeEvent triggerd by a GainControl
	 * @see javax.media.GainControl
	 * @see javax.media.GainChangeEvent
	 */
	public void gainChange(GainChangeEvent e) {
		SwingUtilities.invokeLater(new SetStateThread(e.getLevel()));
	}

	/*
	 * Runnable that calls setState and is run on AWT event queue.
	 */
	class SetStateThread implements Runnable {
		float level;

		public SetStateThread(float level) {
			this.level = level;
		}

		public void run() {
			setState(level);
		}
	}

	/*
	 * Isolate GUI update so that it can be run on separate thread. This method
	 * simply sets the current state of the gain control buttons.
	 */
	private void setState(float level) {
		AbstractGainButtonPanel gbp = (AbstractGainButtonPanel) getControlComponent();
		gbp.getGainIncreaseButton().setEnabled(level < 1.0f);
		gbp.getGainDecreaseButton().setEnabled(level > 0.0f);
	}

	/**
	 * Type-safe way to set Control Component and control listener.
	 * 
	 * @param bp
	 *            An AbstractGainButtonPanel that serves as Control component.
	 * @param listener
	 *            An ActionListener that implements Control semantics.
	 */
	public void setComponentAndListener(AbstractGainButtonPanel bp,
			ActionListener listener) {
		super.setComponentAndListener(bp, listener);
	}

	/**
	 * Type-safe way to set Control Component.
	 * 
	 * @param bp
	 *            An AbstractGainButtonPanel that serves as Control component.
	 */
	public void setComponent(AbstractGainButtonPanel bp) {
		super.setComponent(bp);
	}

	/**
	 * Type-safe way to set Control listener.
	 * 
	 * @param listener
	 *            An ActionListener that implements gain control button
	 *            semantics.
	 */
	public void setControlListener(ActionListener listener) {
		super.setControlListener(listener);
	}
}
